package com.dunshanasync.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author .dunshan
 * @program: dunshan-pef
 * @description: 请求
 * @date 2021-11-05 13:10:58
 */
public class HttpClinetUtils {

    /**
     * Enum for http method.
     *
     * @author Mervin (zhangmofei@jd.com
     */
    private enum HttpMethod {

        GET,
        POST,
        PUT,
        PATCH,
        DELETE,
        HEAD;

        /**
         * Build http request
         *
         * @param uri         request url address
         * @param version     http version
         * @param contentType content type
         * @param data        request data
         * @return inited http request instance
         */
        private HttpUriRequest buildHttpRequest(URI uri, HttpVersion version, ContentType contentType, Map<String, Object> data) {

            HttpEntity entity = null;
            if (data != null) {
                if (ContentType.FORM.equals(contentType)) {
                    entity = buildFormBody(data);
                } else if (ContentType.JSON.equals(contentType)) {
                    entity = buildJsonBody(data);
                }
            }

            HttpUriRequest request;
            switch (this) {
                case POST:
                    HttpPost httpPost = new HttpPost(uri);
                    httpPost.setProtocolVersion(version);
                    httpPost.setEntity(entity);
                    request = httpPost;
                    break;
                case PUT:
                    HttpPut httpPut = new HttpPut(uri);
                    httpPut.setProtocolVersion(version);
                    httpPut.setEntity(entity);
                    request = httpPut;
                    break;
                case PATCH:
                    HttpPatch httpPatch = new HttpPatch(uri);
                    httpPatch.setProtocolVersion(version);
                    httpPatch.setEntity(entity);
                    request = httpPatch;
                    break;
                case DELETE:
                    HttpDelete httpDelete = new HttpDelete(uri);
                    httpDelete.setProtocolVersion(version);
                    request = httpDelete;
                    break;
                case HEAD:
                    HttpHead httpHead = new HttpHead(uri);
                    httpHead.setProtocolVersion(version);
                    request = httpHead;
                    break;
                default:
                    HttpGet httpGet = new HttpGet(uri);
                    httpGet.setProtocolVersion(version);
                    request = httpGet;
            }

            return request;
        }
    }

    /**
     * Enum for content type
     *
     * @author Mervin (zhangmofei@jd.com)
     */
    public enum ContentType {

        FORM("application/x-www-form-urlencoded"),
        FILE("multipart/form-data"),
        JSON("application/json");

        private final String value;

        ContentType(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }
    }

    /**
     * Send request to target url with specified method and http version.
     *
     * @param url         url address
     * @param data        request data
     * @param httpMethod  request method
     * @param contentType content type
     * @return response of target server
     */
    private static HttpResponse sendHttpRequest(String url, Map<String, Object> data, Map<String, Object> headers,
                                                HttpMethod httpMethod, ContentType contentType, HttpVersion httpVersion,
                                                HttpClientConnectionManager connectionManager)
            throws IllegalArgumentException, IOException {

        // validate arguments

        // check url
        if (!RegexUtil.validateUrl(url)) {
            throw new IllegalArgumentException("Invalid url address.");
        }

        // check method
        if (httpMethod == null) {
            // default method is GET
            httpMethod = HttpMethod.GET;
        }

        // check http version
        if (httpVersion == null) {
            // default version is Http 1.1
            httpVersion = HttpVersion.HTTP_1_1;
        }

        // check content type
        if (contentType == null) {
            contentType = ContentType.FORM;
        }

        // merge data to url if method is GET or DELETE.
        if (httpMethod.equals(HttpMethod.GET) || httpMethod.equals(HttpMethod.DELETE)) {
            url = mergeUrlAndData(url, data);
        }

        URI requestUri;
        try {
            requestUri = new URI(url);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("Invalid url address.");
        }

        final HttpUriRequest request = httpMethod.buildHttpRequest(requestUri, httpVersion, contentType, data);

        // Add header
        if (headers != null) {
            headers.entrySet().forEach(p -> request.setHeader(p.getKey(), p.getValue().toString()));
        }

        // Content-Type
        request.setHeader("Content-Type", contentType.getValue());

        HttpClientBuilder httpClientBuilder = HttpClients.custom();

        if (connectionManager != null) {
            httpClientBuilder.setConnectionManager(connectionManager);
        }

        return httpClientBuilder.build().execute(request);
    }

    /**
     * Send get request
     *
     * @param url request url
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse get(String url) throws IOException {
        return get(url, null);
    }

    /**
     * Send get request
     *
     * @param url  request url
     * @param data request data
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse get(String url, Map<String, Object> data) throws IOException {
        return get(url, data, null);
    }

    /**
     * Send get request
     *
     * @param url     request url
     * @param data    request data
     * @param headers headers
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse get(String url, Map<String, Object> data, Map<String, Object> headers) throws IOException {
        return get(url, data, headers, null);
    }

    /**
     * Send get request with specified connection manager
     *
     * @param url               request url
     * @param data              request data
     * @param headers           headers
     * @param connectionManager connection manager
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse get(String url, Map<String, Object> data, Map<String, Object> headers,
                                   HttpClientConnectionManager connectionManager) throws IOException {
        return sendHttpRequest(url, data, headers, HttpMethod.GET, ContentType.FORM, HttpVersion.HTTP_1_1,
                connectionManager);
    }

    /**
     * Send post request
     *
     * @param url         request url
     * @param data        request data
     * @param contentType content type
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse post(String url, Map<String, Object> data, ContentType contentType)
            throws IOException {
        return post(url, data, null, contentType, null);
    }

    /**
     * Send post request
     *
     * @param url         request url
     * @param data        request data
     * @param headers     headers
     * @param contentType content type
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse post(String url, Map<String, Object> data, Map<String, Object> headers, ContentType contentType)
            throws IOException {
        return post(url, data, headers, contentType, null);
    }

    /**
     * Send post request with specified connection manager
     *
     * @param url               request url
     * @param data              request data
     * @param contentType       content type
     * @param connectionManager connection manager
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse post(String url, Map<String, Object> data, ContentType contentType,
                                    HttpClientConnectionManager connectionManager) throws IOException {
        return sendHttpRequest(url, data, null, HttpMethod.POST, contentType, HttpVersion.HTTP_1_1,
                connectionManager);
    }

    /**
     * Send post request with specified connection manager
     *
     * @param url               request url
     * @param data              request data
     * @param headers           headers
     * @param contentType       content type
     * @param connectionManager connection manager
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse post(String url, Map<String, Object> data, Map<String, Object> headers, ContentType contentType,
                                    HttpClientConnectionManager connectionManager) throws IOException {
        return sendHttpRequest(url, data, headers, HttpMethod.POST, contentType, HttpVersion.HTTP_1_1,
                connectionManager);
    }

    /**
     * Send put request
     *
     * @param url         request url
     * @param data        request data
     * @param contentType content type
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse put(String url, Map<String, Object> data, ContentType contentType)
            throws IOException {
        return put(url, data, null, contentType, null);
    }

    /**
     * Send put request
     *
     * @param url         request url
     * @param data        request data
     * @param headers     headers
     * @param contentType content type
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse put(String url, Map<String, Object> data, Map<String, Object> headers, ContentType contentType)
            throws IOException {
        return put(url, data, headers, contentType, null);
    }

    /**
     * Send put request with specified connection manager
     *
     * @param url               request url
     * @param data              request data
     * @param contentType       content type
     * @param connectionManager connection manager
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse put(String url, Map<String, Object> data, ContentType contentType,
                                   HttpClientConnectionManager connectionManager) throws IOException {
        return sendHttpRequest(url, data, null, HttpMethod.PUT, contentType, HttpVersion.HTTP_1_1, connectionManager);
    }

    /**
     * Send put request with specified connection manager
     *
     * @param url               request url
     * @param data              request data
     * @param headers           headers
     * @param contentType       content type
     * @param connectionManager connection manager
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse put(String url, Map<String, Object> data, Map<String, Object> headers, ContentType contentType,
                                   HttpClientConnectionManager connectionManager) throws IOException {
        return sendHttpRequest(url, data, headers, HttpMethod.PUT, contentType, HttpVersion.HTTP_1_1, connectionManager);
    }

    /**
     * Send delete request
     *
     * @param url request url
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse delete(String url)
            throws IOException {
        return delete(url, null);
    }

    /**
     * Send delete request
     *
     * @param url  request url
     * @param data data
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse delete(String url, Map<String, Object> data)
            throws IOException {
        return delete(url, data, null);
    }

    /**
     * Send delete request with specified connection manager
     *
     * @param url               request url
     * @param connectionManager connection manager
     * @param data              data
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse delete(String url, Map<String, Object> data, HttpClientConnectionManager connectionManager)
            throws IOException {
        return delete(url, data, null, connectionManager);
    }

    /**
     * Send delete request with specified connection manager
     *
     * @param url               request url
     * @param data              data
     * @param headers           headers
     * @param connectionManager connection manager
     * @return http response of server
     * @throws IOException connection problems
     */
    public static HttpResponse delete(String url, Map<String, Object> data, Map<String, Object> headers,
                                      HttpClientConnectionManager connectionManager) throws IOException {
        return sendHttpRequest(url, data, headers, HttpMethod.DELETE, null, HttpVersion.HTTP_1_1, connectionManager);
    }

    /**
     * Build form body
     *
     * @param data data map
     * @return entity
     */
    private static HttpEntity buildFormBody(Map<String, Object> data) {
        List<NameValuePair> nameValuePairs = new ArrayList<>(data.size());
        data.entrySet().forEach(element ->
                nameValuePairs.add(new BasicNameValuePair(element.getKey(), element.getValue().toString())));
        try {
            return new UrlEncodedFormEntity(nameValuePairs, "utf-8");
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    /**
     * Build json body
     *
     * @param data data map
     * @return entity
     */
    private static HttpEntity buildJsonBody(Map<String, Object> data) {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return new StringEntity(objectMapper.writeValueAsString(data));
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Merge url with data map
     *
     * @param url  url address
     * @param data data map
     * @return merged url
     */
    private static String mergeUrlAndData(String url, Map<String, Object> data) {
        if (data == null) {
            return url;
        }
        Set<Map.Entry<String, Object>> dataEntrySet = data.entrySet();
        if (dataEntrySet.size() > 0) {
            StringBuilder paramBuilder = new StringBuilder();
            dataEntrySet.forEach(element -> {
                try {
                    paramBuilder.append(
                            String.format("%s=%s&",
                                    URLEncoder.encode(element.getKey(), "utf-8"),
                                    URLEncoder.encode(element.getValue().toString(), "utf-8")));
                } catch (UnsupportedEncodingException e) {
                    noOp();
                }
            });
            if (paramBuilder.length() > 0) {
                url = String.format("%s?%s", url, paramBuilder.toString());
            }
        }
        return url;
    }

    private static void noOp() {

    }
}
